using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;

namespace ProjectEuler.Core
{
    public class Problem59 : IProjectEulerProblem
    {
        protected IEnumerable<int> FindOriginalTextAsciiValues()
        {
            foreach (var bruteKey in BruteKeys)
            {
                var decrypt = XorDecrypt(EncryptedText, bruteKey);
                var textDecrypt = AsciiString(decrypt);
                var wordsDecrypt = GetWords(textDecrypt);
                if (ValidWords(wordsDecrypt) > 10)
                {
                    return textDecrypt.Select(c => (int) c);
                }
            }
            throw new Exception("Unable to decrypt");
        }

        private int ValidWords(IEnumerable<string> wordsDecrypt)
        {
            return wordsDecrypt.Count(word => Words.Value.Contains(word.ToLower()));
        }

        private IEnumerable<string> GetWords(string textDecrypt)
        {
            return textDecrypt.Split(' ');
        }

        private string AsciiString(IEnumerable<int> decrypt)
        {
            return string.Join("", decrypt.Select(d => (char)d));
        }

        protected IEnumerable<int> XorDecrypt(IEnumerable<int> encryptedData, char[] key)
        {
            var intkey = key.ToArray();
            var decrypted = new List<int>();
            var currentint = 0;

            foreach (var letter in encryptedData)
            {
                var decryptedLetter = letter ^ intkey[currentint];
                decrypted.Add(decryptedLetter);
                currentint++;
                if (currentint == 3) currentint = 0;
            }
            return decrypted;
        }

        protected IEnumerable<int> EncryptedText
        {
            get
            {
                var all = File.ReadAllText("GivenFiles\\cipher1.txt");
                return all.Split(',').Select(c => int.Parse(c));
            }
        }

        protected IEnumerable<char[]> BruteKeys
        {
            get
            {
                return Words.Value.Where(w => w.Length == 3).Select(w => w.ToCharArray());
            }
        }

        protected Lazy<IEnumerable<string>> Words
        {
            get
            {
                Func<IEnumerable<string>> lazyLoader = () =>
                                                           {
                                                               var lines = File.ReadAllText("GivenFiles\\words.txt");
                                                               var quotedWords = lines.Split(',');
                                                               return quotedWords.Select(w => (w.Replace("\"", "").ToLower()));
                                                           };
                return new Lazy<IEnumerable<string>>(lazyLoader, false);
            }
        }

        public int Number
        {
            get { return 59; }
        }

        public string Description
        {
            get { return "Decrypt the message and find the sum of the ASCII values in the original text."; }
        }

        public string Answer
        {
            get { return FindOriginalTextAsciiValues().Sum().ToString(); }
        }
    }

    [TestFixture]
    public class Problem59Tests : Problem59
    {
        [Test]
        public void Verify_first_and_last_character_are_correct()
        {
            var first = EncryptedText.First();
            var last = EncryptedText.Last();

            Assert.AreEqual(79,first);
            Assert.AreEqual(73,last);
        }

        [Test]
        public void Can_xor_decrypt_and_encrypt()
        {
            // given: 65 XOR 42 = 107, then 107 XOR 42 = 65
            // 42 is ascii code for '*'

            var encrypt = XorDecrypt(new[] {65}, new [] {'*'});
            Assert.AreEqual(107 ,encrypt.Single());

            var decrypt = XorDecrypt(new[] {107}, new [] {'*'});
            Assert.AreEqual(65, decrypt.Single());
        }
    }
}